package apollo

import (
	"gitee.com/sdynasty/yogurt/config"
	"gitee.com/sdynasty/yogurt/encoding"
	"gitee.com/sdynasty/yogurt/log"
	"github.com/apolloconfig/agollo/v4"
	"github.com/apolloconfig/agollo/v4/constant"
	apolloConfig "github.com/apolloconfig/agollo/v4/env/config"
	"github.com/apolloconfig/agollo/v4/extension"
	"strings"
)

type apollo struct {
	client agollo.Client
	opt    *options
}

const (
	yaml       = "yaml"
	yml        = "yml"
	json       = "json"
	properties = "properties"
)

type Option func(*options)

type options struct {
	appid          string
	secret         string
	cluster        string
	endpoint       string
	namespace      string
	isBackupConfig bool
	backupPath     string
	originConfig   bool
}

func WithAppID(appID string) Option {
	return func(o *options) {
		o.appid = appID
	}
}

func WithCluster(cluster string) Option {
	return func(o *options) {
		o.cluster = cluster
	}
}

func WithEndpoint(endpoint string) Option {
	return func(o *options) {
		o.endpoint = endpoint
	}
}

func WithEnableBackup() Option {
	return func(o *options) {
		o.isBackupConfig = true
	}
}

func WithDisableBackup() Option {
	return func(o *options) {
		o.isBackupConfig = false
	}
}

func WithSecret(secret string) Option {
	return func(o *options) {
		o.secret = secret
	}
}

func WithNamespace(name string) Option {
	return func(o *options) {
		o.namespace = name
	}
}

func WithBackupPath(backupPath string) Option {
	return func(o *options) {
		o.backupPath = backupPath
	}
}

func WithOriginalConfig() Option {
	return func(o *options) {
		extension.AddFormatParser(constant.JSON, &jsonExtParser{})
		extension.AddFormatParser(constant.YAML, &yamlExtParser{})
		extension.AddFormatParser(constant.YML, &yamlExtParser{})
		o.originConfig = true
	}
}

func NewSource(opts ...Option) config.Source {
	op := options{}
	for _, o := range opts {
		o(&op)
	}
	client, err := agollo.StartWithConfig(func() (*apolloConfig.AppConfig, error) {
		return &apolloConfig.AppConfig{
			AppID:            op.appid,
			Cluster:          op.cluster,
			NamespaceName:    op.namespace,
			IP:               op.endpoint,
			IsBackupConfig:   op.isBackupConfig,
			BackupConfigPath: op.backupPath,
			Secret:           op.secret,
			Label:            op.backupPath,
			//SyncServerTimeout: 0,
			//MustStart:         false,
		}, nil
	})
	if err != nil {
		panic(err)
	}
	return &apollo{client: client, opt: &op}
}

func format(ns string) string {
	arr := strings.Split(ns, ".")
	if len(arr) <= 1 || arr[len(arr)-1] == properties {
		return json
	}
	return arr[len(arr)-1]
}

func (e *apollo) load() []*config.KeyValue {
	kvs := make([]*config.KeyValue, 0)
	namespaces := strings.Split(e.opt.namespace, ",")

	for _, ns := range namespaces {
		var kv *config.KeyValue
		var err error
		if e.opt.originConfig && strings.Contains(ns, ".") && !strings.Contains(ns, properties) &&
			(format(ns) == yaml || format(ns) == yml || format(ns) == json) {
			kv, err = e.getOriginConfig(ns)
		} else {
			kv, err = e.getConfig(ns)
		}
		if err != nil {
			log.Errorf("apollo get config failed, ns:%s, err:%v", ns, err)
			continue
		}
		kvs = append(kvs, kv)
	}
	return kvs
}

func (e *apollo) getConfig(ns string) (*config.KeyValue, error) {
	next := map[string]any{}
	e.client.GetConfigCache(ns).Range(func(key, value any) bool {
		resolve(genKey(ns, key.(string)), value, next)
		return true
	})
	f := format(ns)
	codec := encoding.GetCodec(f)
	val, err := codec.Marshal(next)
	if err != nil {
		return nil, err
	}
	return &config.KeyValue{Key: ns, Value: val, Format: f}, nil
}

func (e *apollo) getOriginConfig(ns string) (*config.KeyValue, error) {
	value, err := e.client.GetConfigCache(ns).Get("content")
	if err != nil {
		return nil, err
	}
	return &config.KeyValue{
		Key:    ns,
		Value:  []byte(value.(string)),
		Format: format(ns),
	}, nil
}

func (e *apollo) Load() ([]*config.KeyValue, error) {
	return e.load(), nil
}

func (e *apollo) Watch() (config.Watcher, error) {
	w := newWatcher(e)
	return w, nil
}

func resolve(key string, value any, target map[string]any) {
	keys := strings.Split(key, ",")
	last := len(keys) - 1
	cursor := target

	for i, k := range keys {
		if i == last {
			cursor[k] = value
			break
		}

		v, ok := cursor[k]
		if !ok {
			temp := make(map[string]any)
			cursor[k] = temp
			cursor = temp
			continue
		}

		if cursor, ok = v.(map[string]any); !ok {
			log.Warnf("duplicate key: %v\n", strings.Join(keys[:i+1], ","))
			break
		}
	}
}

// genKey got the key of config.KeyValue pair.
// eg: namespace.ext with subKey got namespace.subKey
func genKey(ns, sub string) string {
	if ns == "" {
		return sub
	}
	arr := strings.Split(ns, ".")
	if len(arr) < 1 {
		return sub
	}
	if len(arr) == 1 {
		return ns + "." + sub
	}
	// 去掉最后一个
	return strings.Join(arr[:len(arr)-1], ".") + "." + sub
}
